foitzik.dev // my collection of random things and thoughts

Using nix to build Caddy with plugins

I found myself wanting to use Cloudflare's DNS (API) to obtain a bunch of certificates for multiple reverse proxy targets hitting my Caddy.

Easy. https://caddyserver.com/docs/modules/dns.providers.cloudflare

Ok, but what is this?

xcaddy build --with github.com/caddy-dns/cloudflare

Does not matter, the very great community of nix/OS already made this something to NOT think about.

For me, I wanted caddy to run in a container. So in order to get caddy with the plugin I want I simply create:

pkgs.caddy.withPlugins {
    plugins = [ "github.com/caddy-dns/[email protected]" ];
    hash = "sha256-F/FAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKE=";
  };

This will produce the derivation that build caddy WITH that plugin. Now you can "simply" put this into a container using pkgs.dockerTools.

# .... inside pkgs.dockerTools.buildImage, for example
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      pathsToLink = [
        "/bin"
        "/etc"
      ];
      paths = copyToRootPaths; # should include your derivation and caCerts and other stuff
    };
# ....

Hidden Problems and the beauty of nix/OS

Now, you like "stable" software, therefore you are currently on nixOS 25.11. Oh, well the above nix configuration will fail to build. Go version 1.26 required. Oh no. pkgs.go points to 1.25.x and pkgs.caddy (transient buildGoModule) points to that as well. I guess I need to switch to nixos-unstable?

No, not necessarily. You could add nixos-unstable as new input (when using flake) and consume pkgs-unstable.caddy (if you name it like that) instead. However, for this scenario this is a bit cumbersome and very much not necessary.

Since pkgs.caddy is basically buildGoModule we can simply utilize that.

  caddyGo126 = pkgs.caddy.override {
    buildGoModule = pkgs.buildGo126Module;
  };

  caddyWithPluginsGo126 = pkgs.callPackage "${pkgs.path}/pkgs/by-name/ca/caddy/plugins.nix" {
    caddy = caddyGo126;
    go = pkgs.go_1_26;
  };

  caddyWithCloudflare = caddyWithPluginsGo126 {
    plugins = [
      "github.com/caddy-dns/[email protected]"
    ];
    hash = "sha256-Olz4W84Kiyldy+JtbIicVCL7dAYl4zq+2rxEOUTObxA=";
  };

The two "magically" things here are:

  1. ${pkgs.path} etc., which is a neat way to reference "internal" (not really internal) nix files from nixpkgs. In this case in order to use the plugins.nix but with our own go derivation.
  2. "Sticking things together" and using .override, which is a common thing in nix (the sticking things together)

NOTE: Using overwrite here is "mildly ok", because this means the whole derivation of the package (caddy) now needs to be rebuilt and can only utlize the public nixOS cache in so far as we use cached/prebuilt items. In our case, we rebuild caddy anyway (at least partially) w.r.t. to the plugins, so this is FINE. This can be dangerous when this would be something more complex, e.g., a browser or something that takes a long time to build, because now this get's rebuilt EVERYTIME we do not have the result in cache and might not get much cached pre-requisites from nixOS cache. So keep that in mind.